using System;
using System.IO;
using System.Threading;
using System.Collections;
using XTCast.Mp3;

namespace XTCast.Radio {
	
	/// <summary>
	/// A buffer for raw binary data, including the methods to write to (fill) the buffer from
	/// an InputStream, and to read the data from the Buffer to an OutputStream.
	/// This class manages the synchronization between a single writer (the DJ thread) and multiple readers (the Transmitter threads):
	/// Multiple readers can access the buffer at once.
	/// Only one writer can access it at once.
	/// </summary>
	
	public class Buffer {

		public delegate void WriteTitleDelegate(System.IO.Stream outs, Track track);

		public Buffer(BufferManager bm) {
			this.bm = bm;
		}

		public Buffer Clone() {
			Buffer newbuf = new Buffer(bm);
			newbuf.fBytes=fBytes;
			newbuf.fLength=fLength;
			newbuf.fLengthMillis=fLengthMillis;
			newbuf.fTrack=fTrack;
			return newbuf;
		}

		static public void Initialize(BufferConfig c) {
			// Get buffer size from defaults:
			sSize = Math.Max(1024,c.Size);
			// Get buffer milliseconds from defaults:
			sLength = c.Length;
		}

		/// Returns the current length in bytes of the data in the Buffer
		virtual public int Length {
			get {
				return fLength;
			}
		}

		virtual public int BitRate {
			get {
				return (fLength*8000)/fLengthMillis;
			}
		}

		virtual public int NumReaders {
			get {
				return fReaders;
			}
			
		}

		virtual public int LengthMillis {
			get {
				return fLengthMillis;
			}
			
		}

		virtual public Track Track {
			get {
				return fTrack;
			}
		}

		bool invalid = false;
		virtual public bool Invalid { 
			get { return invalid; }
			set { invalid=value; }
		}
		
		public override System.String ToString() {
			return "Buffer[" + ((fLength + 512) / 1024) + "K" + (fReaders > 0?", " + fReaders + " reader(s)":"") + "]";
		}
		
		/// <summary>Fills the buffer with data read from the stream (up to the maximum buffer size.)</summary>
		/// <returns>True if data was read, or false if at EOF</returns>
		public virtual bool ReadFrom(IFrameReader stream, Track track) {
			fTrack = track;
			try {
				FrameReadInfo readInfo = new FrameReadInfo(sSize,(int)sLength.TotalMilliseconds);
				fBytes = stream.ReadFrames(readInfo);
				fLength = System.Math.Max(0, readInfo.ActualLength);
				fLengthMillis = readInfo.ActualMillis;
				Log.Verbose(this, "Read (bytes/millis): " + readInfo.ActualLength + "/" + readInfo.ActualMillis);
				return readInfo.ActualLength > 0;
			}
			catch (System.Exception ex) {
				fBytes = null;
				fLength = 0;
				fLengthMillis = 0;
				Log.Error(this,ex);
				return false;
			}
		}
		
		/// <summary>Writes the buffer's data to an OutputStream.</summary>
		/// <returns>The number of milliseconds of real time that this MP3 data represents.</returns>
		public virtual int WriteTo(System.IO.Stream outs) {
			byte[] bytes;
			int length;
			
			lock (this) {
				fReaders++;	
				bytes = fBytes;
				length = fLength;
			}
			try {
				outs.Write(bytes, 0, length); // Write the data:
			}
			finally {
				lock (this) {
					fReaders--;				// Finally decrement the number of readers:
				}
			}
			return fLengthMillis;
		}

		// <summary>Writes the buffer's data to an OutputStream.</summary>
		/// <returns>The number of milliseconds of real time that this MP3 data represents.</returns>
		public virtual int WriteTo(System.IO.Stream outs, int interval, ref int sendBefore, WriteTitleDelegate writeTitle) {
			byte[] bytes;
			int length;
			Track track;
			
			// Block until the buffer is written (to avoid overtaking the DJ thread),
			// then bump the number of readers:
			lock (this) {
				fReaders++;				
				bytes = fBytes;
				length = fLength;
				track = fTrack;
			}
			
			// Write the data:
			try {
				int sent=0, toSend;
				while (sent<length) {
					toSend = sendBefore;
					if (sendBefore>length-sent)
						toSend=length-sent;
					else
						toSend = sendBefore;
					outs.Write(bytes, sent, toSend);
					sent += toSend;
					sendBefore -= toSend;
					if (sendBefore==0) {
						sendBefore = interval;
						writeTitle(outs,track);
					}
				}
			}
			finally {
				lock (this) {
					fReaders--; // Finally decrement the number of readers:
				}
			}
			return fLengthMillis;
		}

		public int Position {
			get { return bm.Position(this); }
		}
		
		private BufferManager bm;	/* The buffer's parent buffer manager */
		private byte[] fBytes;		/* The data buffer. Only the first fLength bytes contain valid data. */
		private int fLength;		/* The length of the valid data in fBytes. */
		private int fLengthMillis;	/* Number of milliseconds represented by buffer */
		private Track fTrack;		/* Track playing */
		private int fReaders;		/* The number of threads reading from the buffer (in the writeTo method) */

		public static int sSize = 32768;	/// Max bytes in buffer
		public static TimeSpan sLength = TimeSpan.FromSeconds(2);	/// Max milliseconds in buffer
	}

	public class BufferException:ApplicationException {
		public BufferException(string msg):base(msg) {}
	}
}
